#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#函数式编程
'''
TMD,学到后面已经没法用简单的英文去表达了,还是用中文吧,已放弃治疗

函数式编程就是一种抽象程度很高的编程范式，纯粹的函数式编程语言编写的函数没有变量，
因此，任意一个函数，只要输入是确定的，输出就是确定的，这种纯函数没有副作用。
而允许使用变量的程序设计语言，由于函数内部的变量状态不确定，同样的输入，可能得到不同的输出，因此，这种函数是有副作用的。
函数式编程的一个特点就是，允许把函数本身作为参数传入另一个函数，还允许返回一个函数！
Python对函数式编程提供部分支持。由于Python允许使用变量，因此，Python不是纯函数式编程语言。
'''

#高阶函数 Higher-order function
'''
一个函数可以接收另一个函数作为参数，这种函数就称之为高阶函数。

首先要知道在python中, 函数名本身就是一个变量:
'''
print(abs(-10))
print(abs)
#可见，abs(-10)是函数调用，而abs是函数本身。
f = abs
print(f(-10))
print(f)
'''
变量f现在已经指向了abs函数本身,也就是说变量可以指向函数。
如果把abs指向其他对象,如abs = 10
那么abs就成了一个number,当然实际代码绝对不能这么写，这里是为了说明函数名也是变量。

注: 由于abs函数实际上是定义在import builtins模块中的，所以要让修改abs变量的指向在其它模块也生效，要用import builtins; builtins.abs = 10

既然变量可以指向函数，函数的参数能接收变量，那么一个函数就可以接收另一个函数作为参数，这种函数就称之为高阶函数。
'''
def add(x, y, f):
    return f(x) + f(y)

print(add(-5, 6, abs))
print()


# ---------------------------------------------------------------------------------------------------
# map

'''
map()函数接收两个参数，一个是函数，一个是Iterable，map将传入的函数依次作用到序列的每个元素，并把结果作为新的Iterator返回。
I has a function:f(x) = x^2, if want to put this function on a list of all elements, can use the map () function.
'''
def f(x):
    return x*x
#help(map)      # =>  map(func, *iterables) --> map object
r = map(f, range(1,10))
L = list(r)
print(L)
#print(list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])))   #也可精简成一行
'''
map()传入的第一个参数是f，即函数对象本身。由于结果r是一个Iterator，Iterator是惰性序列，因此通过list()函数让它把整个序列都计算出来并返回一个list。

你可能会想，不需要map()函数，写一个循环，也可以计算出结果：
'''
L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
    L.append(f(n))
print(L)
'''
的确可以，但是，从上面的循环代码，能一眼看明白"把f(x)作用在list的每一个元素并把结果生成一个新的list"吗？

所以，map()作为高阶函数，事实上它把运算规则抽象了，
因此，我们不但可以计算简单的f(x)=x2，还可以计算任意复杂的函数，
比如，把这个list所有数字转为字符串,
只需要一行代码
'''
print(list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))) 


print('\n')
# ---------------------------------------------------------------------------------------------------
# reduce
'''
在看来reduce: 把一个函数作用在一个序列[x1, x2, x3, ...]上，这个函数必须接收两个参数，reduce把结果继续和序列的下一个元素做累积计算.
其效果就是： reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
'''
from functools import reduce

#help(reduce)      # =>   reduce(function, sequence[, initial]) -> value
def add(x, y):
    return x + y
num = reduce(add, [1, 3, 5, 7, 9])
print(num)
'''
当然求和运算可以直接用Python内建函数sum()，没必要动用reduce。
但是如果要把序列[1, 3, 5, 7, 9]变换成整数13579，reduce就可以派上用场：
'''
def fn(x, y):
    return x * 10 + y
num = reduce(fn, [1, 3, 5, 7, 9])
print(num)

'''
这个例子本身没多大用处，但是，如果考虑到字符串str也是一个序列，对上面的例子稍加改动，配合map()，我们就可以写出把str转换为int的函数：
'''
def char2num(s):
    return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
#这里差点看不懂, {}里面是dict, s是dict的key, 相当于return dict[key]
num = reduce(fn, map(char2num, '12344'))
print("'12344' => ",num)

#整理成一个str2int的函数.
#因为几个基础函数在前面定义了, 如果没有定义, 其实str2int函数可以这样整合:

#from functools import reduce
def str2int(s):
    def fn(x, y):
        return x * 10 + y
    def char2num(s):
        return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
    return reduce(fn, map(char2num, s))
#可以在函数里面定义函数
print("'754733' => ",str2int('754733'))

#还可以用lambda函数进一步简化成：
#from functools import reduce
def str2int(s):

    def char2num(s):
        return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))

print("'754733' => ",str2int('754733'))

"""
lambda的意思还没学,先个人分析下
很明显'lambda x, y: x * 10 + y'是reduce的第一个参数,也就是说这里等价于fn函数
那么x,y就相当fn的入参了,冒号':'后面跟了fn函数的返回值公式
可以想象lambda的作用就是把冒号前的变量代入冒号后的公式计算出结果.我果然是天才,不好意思,情不自禁.
"""

#输入：['adam', 'LISA', 'barT']，输出：['Adam', 'Lisa', 'Bart']：

def normalize(name):
    return name.title()
    
print(list(map(normalize, ['adam', 'LISA', 'barT'])))
#请编写一个prod()函数，可以接受一个list并利用reduce()求积：
def prod(L):
    return reduce(lambda x,y:x*y,L)
print('3 * 5 * 7 * 9 =', prod([3, 5, 7, 9]))

#利用map和reduce编写一个str2float函数，把字符串'123.456'转换成浮点数123.456：

def str2float(s):
    def char2num(s):
        return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '.':None}[s]
    L = list(map(char2num,s))
    L.pop(s.index('.'))
    return reduce(lambda x, y: x * 10 + y, L)/(10**(len(s)-s.index('.')-1))
print("'123.456' => ",str2float('123.456'))
print("'12.3456' => ",str2float('12.3456'))


#---------------果然是life is short------------------------------------------------------------------
#from functools import reduce
def str2float(s):
    def char2num(s):
        return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '.':None}[s]
    return reduce(lambda x, y: x * 10 + y, map(char2num,[x for x in s if x != '.']))/(10**(len(s)-s.index('.')-1))
print("'123.456' => ",str2float('123.456'))
print("'12.3456' => ",str2float('12.3456'))
print('\n')


# ---------------------------------------------------------------------------------------------------
# filter
'''
Python内建的filter()函数用于过滤序列。
和map()类似，filter()也接收一个函数和一个序列。
和map()不同的是，filter()把传入的函数依次作用于每个元素，然后根据返回值是True还是False决定保留还是丢弃该元素。
'''
#help(filter)
#在一个list中，删掉偶数，只保留奇数，可以这么写：
def is_odd(n):
    return n % 2 == 1
L = list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
print(L)
'''
可见用filter()这个高阶函数，关键在于正确实现一个“筛选”函数。
注意到filter()函数返回的是一个Iterator，也就是一个惰性序列，所以要强迫filter()完成计算结果，需要用list()函数获得所有结果并返回list。
'''
#把一个序列中的空字符串删掉，可以这么写：
def not_empty(s):
    return s and s.strip()

L = list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))
print(L)

'''
计算素数的一个方法是埃氏筛法，它的算法理解起来非常简单：

首先，列出从2开始的所有自然数，构造一个序列：

2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

取序列的第一个数2，它一定是素数，然后用2把序列的2的倍数筛掉：

3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

取新序列的第一个数3，它一定是素数，然后用3把序列的3的倍数筛掉：

5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

取新序列的第一个数5，然后用5把序列的5的倍数筛掉：

7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

不断筛下去，就可以得到所有的素数。注意前面要加上2
'''
#先构造一个从3开始的奇数序列：

def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n 
        
#然后定义一个筛选函数
'''
这里要说明 想了很久 注意n不是it传过来的,x才是it传过来的序列 
n是固定的,已经在前面从it里面取出并打印了 
这里通过取余把序列里面整除的数给过滤掉 非常巧妙!
'''
def _not_divisible(n):
    return lambda x: x % n > 0      
        
def primes():
    yield 2
    it = _odd_iter()
    while True:
        n = next(it)
        yield n
        it = filter(_not_divisible(n), it) 
        
 
# 打印1000以内的素数:
for x in primes():
    if x < 100:
        print(x)
    else:
        break
#filter(not_empty, ['A', '', 'B', None, 'C', '  '])
#L = list())
#print(L)


it = iter([1,2,3,4,5,6,7])
n = next(it)
print(list(it))

#回数是指从左向右读和从右向左读都是一样的数，例如12321，909。请利用filter()滤掉非回数：    
def is_palindrome(n):
    return str(n) == str(n)[::-1]
    
output = filter(is_palindrome, range(1, 1000))
print(list(output))

print('\n')
# ---------------------------------------------------------------------------------------------------
# sorted    排序算法

'''
无论使用冒泡排序还是快速排序，排序的核心是比较两个元素的大小。
如果是数字，我们可以直接比较，但如果是字符串或者两个dict呢？
直接比较数学上的大小是没有意义的，因此，比较的过程必须通过函数抽象出来。
Python内置的sorted()函数就可以对list进行排序：
'''
print(sorted([36, 5, -12, 9, -21]))

'''
此外，sorted()函数也是一个高阶函数，它还可以接收一个key函数来实现自定义的排序，
key指定的函数将作用于list的每一个元素上，并根据key函数返回的结果进行排序。
例如按绝对值大小排序：
'''
print(sorted([36, 5, -12, 9, -21], key=abs))

#默认情况下，对字符串排序，是按照ASCII的大小比较的，由于'Z' < 'a'，结果，大写字母Z会排在小写字母a的前面。 

print(sorted(['bob', 'about', 'Zoo', 'Credit']))

#给sorted传入key函数，即可实现忽略大小写的排序：
print(sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower))


#要进行反向排序，不必改动key函数，可以传入第三个参数reverse=True：
print(sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True))

#sorted()对一组元组按名字排序：
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_name(t):
    return t[0]             #一开始没想到这个答案 表示羞愧 现在明白:sorted函数是根据key返回的结果排序,就算返回123456,那也是跟L对应的123456 最终返回的还是还是后的L 不会变成123456
L2 = sorted(L, key=by_name)
print(L2)
#再按成绩从高到低排序：

def by_score(t):
    return t[1]
L2 = sorted(L, key=by_score,reverse=True)
print(L2)


